import serial
import modbus_tk
import modbus_tk.defines as cst
from PyQt5.QtCore import QObject,pyqtSignal
from modbus_tk import modbus_rtu, modbus_tcp, modbus_rtu_over_tcp, modbus
import time,re,os,configparser
import logging
from ClientUi.plc.modbusutils import *
import threading

ModbusMode = ['Modbus RTU', 'Modbus TCP', 'Modbus RTU OVER TCP']

class QueryCommand:

    def __init__(self, command_type: int = cst.COILS, slave_id: int = 1, start_address: int = 0, quantity: int = 1):
        # 查询命令类型:4种数据类型 'coils' 'Discrete Inputs' 'Holding Registers' 'Input Registers'
        self.commandtype = command_type
        self.slaveid = slave_id
        self.start_address = start_address
        self.quantity = quantity
        self.queryed_data = []


    def setData(self,value):
        self.queryed_data = value

    def getData(self):
        return self.queryed_data

    def clearData(self):
        if len(self.queryed_data) > 0:
            self.queryed_data.clear()

class ModbusMaster(QObject):

    # onQueryedData = lambda self, data: None
    onQueryedData = pyqtSignal(list)
    def __init__(self, mode: str):
        super(ModbusMaster, self).__init__()
        self.config = \
            {
                "mode" : 'RTU',
                "port" : 'com1',
                "baudrate" : '9600',
                "bytesize" : '8',
                "parity" : "None",
                "stopbits" : "1",
                "flowcontrol" : "None",
                "rts" : False,
                "dtr" : False,
                "ip_address" : '172.0.0.1',
                "ip_port" : '503'
            }
        self.querycommandconfig = {}
        # modbus 三种模式，modbus TCP,modbus RTU,modbus RTU over TCP
        self.mastermode = mode
        self._is_open = False
        self.master = None
        self.ser = serial.Serial()
        self.ipaddress = None
        self.ipport = 503
        self.logger = modbus_tk.utils.create_logger("console", level=logger.DEBUG)
        self.querycommand = []
        self.conf_parse = configparser.ConfigParser()  # 配置文件解析ConfigParser对象
        self.tt = HTime()
        self.lock = threading.Lock()
        self.query_data_running = False
        self.stopQueryFlag = False
        self.queryProcessThread = None  # modbus数据查询线程
        pass

    def is_open(self):
        return self._is_open

    def addQueryCommand(self, command: QueryCommand):
        if command == None:
            return
        self.lock.acquire()
        try:
            self.querycommand.append(command)
        finally:
            self.lock.release()

    def delQueryCommand(self, index:int):
        self.lock.acquire()
        try:
            self.querycommand.pop(index)
        finally:
            self.lock.release()

    def getQuerryCommand(self) -> list:
        self.lock.acquire()
        try:
            commands = self.querycommand
        finally:
            self.lock.release()
            return commands

    def setQueryCommandValue(self, value, index):
        self.lock.acquire()
        try:
            self.querycommand[index].setData(value)
        finally:
            self.lock.release()

    def SetStopQueryFlag(self, flag: bool):
        self.lock.acquire()
        try:
            self.stopQueryFlag = flag
        finally:
            self.lock.release()

    def GetStopQueryFlag(self):
        self.lock.acquire()
        try:
            flag = self.stopQueryFlag
        finally:
            self.lock.release()
            return flag

    def setQueryRunningflag(self, flag: bool):
        self.lock.acquire()
        try:
            self.query_data_running = flag
        finally:
            self.lock.release()

    def getQueryRunningflag(self):
        self.lock.acquire()
        try:
            flag = self.query_data_running
        finally:
            self.lock.release()
            return flag

    def openCloseMaster(self,open: bool = True):
        try:
            if self.master is not None:
                if open:
                    self.master.open()
                    self._is_open = True
                else:
                    self.master.close()
                    self.master = None
                    self._is_open = False
        except Exception as e:
            if open:
                print('open modbus master error')
                self._is_open = False
            else:
                print('close modbus master error')
                self._is_open = True


    def setSerialConfig(self, conf_type, value: str) -> tuple:
        msg = ''
        if conf_type not in ["port", "baudrate", "bytesize", "parity", "stopbits", "flowcontrol", "rts", "dtr"]:
            msg = "参数类型错误"
            print(msg)
            return -1, msg
        try:

            if conf_type == "port":
                # 检测是否为com(0~999)
                v = re.findall('^(com0|com[1-9]\d\d|com[1-9]\d)$',value,re.I)
                if len(v) < 0:
                    msg = "port参数错误：value={}".format(value)
                    print(msg)
                    return -1, msg
                self.ser.port = value
            elif conf_type == "baudrate":
                baud = int(value)
                self.ser.baudrate = baud
            elif conf_type == "bytesize":
                bytesize = int(value)
                self.ser.bytesize = bytesize
            elif conf_type == "parity":
                self.ser.parity = value[0]
            elif conf_type == "stopbits":
                self.ser.stopbits = float(value)
            elif conf_type == "flowcontrol":
                if value == "XON/XOFF":
                    self.ser.xonxoff = True
                else:
                    self.ser.xonxoff = False
                if value == "RTS/CTS":
                    self.ser.rtscts = True
                else:
                    self.ser.rtscts = False
                if value == "DSR/DTR":
                    self.ser.dsrdtr = True
                else:
                    self.ser.dsrdtr = False
            elif conf_type == "rts":
                pass
            elif conf_type == "dtr":
                pass
        except Exception as e:
            msg = "设置串口参数错误：{}={}".format(conf_type,value)
            print(msg)
            return -1, msg
        else:
            return 0, msg
    # 读取串口配置————配置文件和section是否存在
    def read_cfg(self, path:str, dirname :str , filename:str):
       self.cfg_path = path
       self.cfg_dir = dirname
       self.cfg_filename = filename
       real_path = os.path.join(self.cfg_path, self.cfg_dir, self.cfg_filename)  # 获取配置文件路径 join用于连接两个或更多的路径
       # 判断读取配置文件是否正常 如果读取文件正常
       if self.conf_parse.read(real_path):
           # 判断读取section是否正常
           try:
               # 获取connect_setting section  返回一个配置字典
               connect_items = self.conf_parse.items('connect_setting')
               self.config = dict(connect_items)
               print(self.config)

           # 如果没有找到section
           except configparser.NoSectionError:
               self.conf_parse.add_section('connect_setting')  # 添加section
               self.conf_parse.write(open(real_path, 'w'))  # 保存到配置文件
               return None

           try:
               # 获取query_command section  返回一个配置字典
               command_items = self.conf_parse.items('query_command')
               self.querycommandconfig = dict(command_items)
               num = int(self.querycommandconfig['command_num'])
               for i in range(num):
                   name = 'command{}'.format(i)
                   type = self.querycommandconfig[name + '_type']
                   slaveid = self.querycommandconfig[name + '_slaveid']
                   startaddress = self.querycommandconfig[name + '_startaddress']
                   quantity = self.querycommandconfig[name + '_quantity']
                   querycommand = QueryCommand(type, slaveid, startaddress, quantity)
                   self.querycommand.append(querycommand)

               print(self.querycommandconfig)

           # 如果没有找到section
           except configparser.NoSectionError:
               self.conf_parse.add_section('query_command')  # 添加section
               self.conf_parse.write(open(real_path, 'w'))  # 保存到配置文件
               return None
       # 读取文件异常
       else:
           # 判断setting目录是否存在 不存在的话新建目录
           if not os.path.exists(os.path.join(self.cfg_path, self.cfg_dir)):
               os.makedirs(os.path.join(self.cfg_path, self.cfg_dir))
           self.conf_parse.add_section('connect_setting')  # 添加section
           self.conf_parse.set('connect_setting', 'mode', self.config['mode'])
           self.conf_parse.set('connect_setting', 'port', self.config['port'])
           self.conf_parse.set('connect_setting', 'baudrate', self.config['baudrate'])
           self.conf_parse.set('connect_setting', 'bytesize', self.config['bytesize'])
           self.conf_parse.set('connect_setting', 'stopbits', self.config['stopbits'])
           self.conf_parse.set('connect_setting', 'parity', self.config['parity'])
           self.conf_parse.set('connect_setting', 'flowcontrol', self.config['flowcontrol'])
           self.conf_parse.set('connect_setting', 'ip_address', self.config['ip_address'])
           self.conf_parse.set('connect_setting', 'ip_port', self.config['ip_port'])
           self.conf_parse.add_section('query_command')  # 添加section
           self.conf_parse.set('query_command', 'command_num', '0')

           self.conf_parse.write(open(real_path, 'w'))  # 保存到配置文件
           return None

    def save_cfg(self):
        # 保存每一项到配置文件
        self.conf_parse.set('connect_setting', 'mode', self.config['mode'])
        self.conf_parse.set('connect_setting', 'port', self.config['port'])
        self.conf_parse.set('connect_setting', 'baudrate', self.config['baudrate'])
        self.conf_parse.set('connect_setting', 'bytesize', self.config['bytesize'])
        self.conf_parse.set('connect_setting', 'stopbits', self.config['stopbits'])
        self.conf_parse.set('connect_setting', 'parity', self.config['parity'])
        self.conf_parse.set('connect_setting', 'flowcontrol', self.config['flowcontrol'])
        self.conf_parse.set('connect_setting', 'ip_address', self.config['ip_address'])
        self.conf_parse.set('connect_setting', 'ip_port', self.config['ip_port'])

        command_num = len(self.querycommand)
        if command_num == 0:
            self.conf_parse.set('query_command', 'command_num', '0')
        else:
            for i in range(command_num):
                self.conf_parse.set('query_command', 'command_num', '0')
                name = 'command{}'.format(i)
                self.conf_parse.set('connect_setting', name + '_type', str(self.querycommand[i].commandtype))
                self.conf_parse.set('connect_setting', name + '_slaveid', str(self.querycommand[i].slaveid))
                self.conf_parse.set('connect_setting', name + '_startaddress', str(self.querycommand[i].start_address))
                self.conf_parse.set('connect_setting', name + '_quantity', str(self.querycommand[i].quantity))

        real_path = os.path.join(self.cfg_path, self.cfg_dir, self.cfg_filename)  # 获取配置文件路径 join用于连接两个或更多的路径
        self.conf_parse.write(open(real_path, 'w'))

    def init(self,
             mode: str = 'modbus RTU',
             port: str = 'com1',
             baudrate: str = '9600',
             bytesize: str = '8',
             parity: str = 'None',
             stopbits: str = '1',
             flowcontrol: str = 'None',
             ipaddress = '127.0.0.1',
             ipport = '503'
             ):
        ret = False
        self.mastermode = mode
        try:
            if self.mastermode == 'Modbus RTU':
                self.initSerial(port,baudrate, bytesize, parity, stopbits, flowcontrol)
                self.master = modbus_rtu.RtuMaster(self.ser)
                self.master.set_timeout(0.1)
                self.master.open()

                return True
            elif self.mastermode == 'Modbus TCP':
                self.ipaddress = ipaddress
                self.ipport = int(ipport)
                self.master = modbus_tcp.TcpMaster(self.ipaddress, self.ipport, 1.0)
                self.master.set_timeout(0.1)
                self.master.open()
                return True
            elif self.mastermode == 'Modbus RTU OVER TCP':
                self.ipaddress = ipaddress
                self.ipport = int(ipport)
                self.master = modbus_rtu_over_tcp.RtuOverTcpMaster(self.ipaddress, self.ipport, 1.0)
                self.master.set_timeout(0.1)
                self.master.open()
                return True
            else:
                print('mode error')
                return False
        except Exception as e:
            pass

        return ret


    def initSerial(self,
                   port: str = 'com1',
                   baudrate: str = '9600',
                   bytesize :str = '8',
                   parity: str = 'None',
                   stopbits: str = '1' ,
                   flowcontrol :str = 'None'
                   ):

        # 1.首先，设置串口参数
        ret = -1
        msg = ''

        self.config['port'] = port
        self.config['baudrate'] = baudrate
        self.config['bytesize'] = bytesize
        self.config['parity'] = parity
        self.config['stopbits'] = stopbits
        self.config['flowcontrol'] = flowcontrol

        for key, value in self.config.items():
            if key in ["port", "baudrate", "bytesize", "parity", "stopbits", "flowcontrol", "rts", "dtr"]:
                ret, msg = self.setSerialConfig(key, value)
                if ret < 0:
                    return ret, msg

    def startStopQuery(self):
        if not self.is_open():
            print('Do not Connect Modbus Slave!')
            return False

        if len(self.getQuerryCommand()) == 0:
            print('未添加查询命令!')
            return False

        if not self.getQueryRunningflag():
            # 开启查询线程
            self.queryProcessThread = threading.Thread(name='query modbus slave thread', target=self.queryProcess)
            self.queryProcessThread.setDaemon(True)
            self.SetStopQueryFlag(False)
            self.queryProcessThread.start()
            return True
        else:
            # 关闭查询线程
            self.SetStopQueryFlag(True)     # 设置停止查询标志
            # 等待查询线程退出，设置最大等待时长
            Elapsed = HTime()
            Elapsed.msBegin()
            timeout = 200   # 最大超时时间为200ms
            Expired = False
            while self.getQueryRunningflag() and not Expired:
                time.sleep(0.001)
                if Elapsed.msLast() > timeout:
                    Expired = True

            return True



    def queryProcess(self):
        self.setQueryRunningflag(True)
        tt = HTime()

        try:
            while self.is_open() and not self.GetStopQueryFlag():
                commands = self.getQuerryCommand()
                if len(commands) <= 0:
                    break

                tt.msBegin()
                for command in commands:
                    ret = self.master.execute(command.slaveid, command.commandtype,
                                              command.start_address, command.quantity)
                    index = commands.index(command)
                    self.setQueryCommandValue(ret, index)
                    print(">>>>ret:",ret)
                    self.logger.info(ret)
                    if not self.is_open() or self.GetStopQueryFlag():
                        self.setQueryRunningflag(False)
                        return
                print('used time:{}ms'.format(tt.msLast()))
                data = []
                for command in commands:
                    data.append(command.getData())

                self.onQueryedData.emit(data)

                # try:
                #     self.onQueryedData(data)
                # except Exception as e:
                #     print("-- error in onQueryedData callback:", e)

        except modbus.ModbusError as exc:
            # modbus 查询出错了
            self.logger.error("%s", exc)
            self.show_modbus_exception(exc.get_exception_code())
        except Exception as e:
            # 其它异常
            print("e3:",e)
            pass

        self.setQueryRunningflag(False)


    def show_modbus_exception(self, code):
        if code == cst.ILLEGAL_FUNCTION:
            print('异常：从站设备不支持此功能码')
        elif code == cst.ILLEGAL_DATA_ADDRESS:
            print('异常：指定的数据地址在从站设备中不存在')
        elif code == cst.ILLEGAL_DATA_VALUE:
            print('异常：指定的数据超过范围或者不允许使用')
        elif code == cst.SLAVE_DEVICE_FAILURE:
            print('异常：从站设备处理响应的过程中，出现未知错误')
        elif code == cst.COMMAND_ACKNOWLEDGE:
            print('确认：从站设备正在处理这个请求，但是需要长持续时间进行这些操作')
        elif code == cst.SLAVE_DEVICE_BUSY:
            print('异常：从属设备忙，正在处理长持续时间的程序命令')
        elif code == cst.MEMORY_PARITY_ERROR:
            print('异常：存储奇偶差错')
        else:
            print('异常：其它异常')

    # 读单个或者多个线圈值DO（位操作）
    def readCoils(self, slaveId: int, start_addr: int, num: int):
        try:
            self.tt.msBegin()
            ret = self.master.execute(slaveId, cst.READ_COILS, start_addr, num)
            print('used time:{}ms'.format(self.tt.msLast()))
            self.logger.info(ret)
            return ret
        except modbus.ModbusError as exc:
            self.logger.error("%s", exc)
            self.show_modbus_exception(exc.get_exception_code())
            return None
        except Exception as e:
            print("e4:",e)


    # 读单个或者多个离散输入值DI（位操作）
    def readDiscreteInputs(self, slaveId: int, start_addr: int, num: int):
        try:
            self.tt.msBegin()
            ret = self.master.execute(slaveId, cst.READ_DISCRETE_INPUTS, start_addr, num)
            print('used time:{}ms'.format(self.tt.msLast()))
            self.logger.info(ret)
            return ret
        except modbus.ModbusError as exc:
            self.logger.error("%s", exc)
            self.show_modbus_exception(exc.get_exception_code())
            return None
        except Exception as e:
            print("e5:",e)

    # 读单个或者多个保持寄存器值（字操作）-- 一般用于保存数据值
    def readHoldingRegisters(self, slaveId: int , start_addr: int , num :int ):
        try:
            self.tt.msBegin()
            ret = self.master.execute(slaveId, cst.READ_HOLDING_REGISTERS, start_addr, num)
            #print('readHoldingRegisters used time:{}ms'.format(self.tt.msLast()))
            #self.logger.info(ret)
            return ret
        except modbus.ModbusError as exc:
            self.logger.error("aa %s", exc)
            self.show_modbus_exception(exc.get_exception_code())
            return None
        except Exception as e:
            print("e6:",e)

    # 读单个或者多个输入寄存器值（字操作）-- 一般存放外部设备状态信息
    def readInputRegisters(self, slaveId: int , start_addr: int , num :int ):
        try:
            self.tt.msBegin()
            ret = self.master.execute(slaveId, cst.READ_INPUT_REGISTERS, start_addr, num)
            print('used time:{}ms'.format(self.tt.msLast()))
            self.logger.info(ret)
            return ret
        except modbus.ModbusError as exc:
            self.logger.error("%s", exc)
            self.show_modbus_exception(exc.get_exception_code())
            return None
        except Exception as e:
            print("e7:",e)

    # 写单个线圈值（DO）（位操作）-- 一般输出某个信号
    def writeSingleCoil(self, slaveId: int , start_addr: int , output_value):
        try:
            self.tt.msBegin()
            ret = self.master.execute(slaveId, cst.WRITE_SINGLE_COIL, start_addr, output_value=output_value)
            print('used time:{}ms'.format(self.tt.msLast()))
            self.logger.info(ret)
            # 判断返回元组信息，ret[0] == 起始地址
            if ret[0] == start_addr:
                # 写入数据成功
                return True
            else:
                return False

        except modbus.ModbusError as exc:
            self.logger.error("%s", exc)
            self.show_modbus_exception(exc.get_exception_code())
            return None
        except Exception as e:
            print("e8:",e)


    # 写多个线圈值（DO）（位操作） -- 一般同时输出多个信号
    def writeMultipleCoil(self, slaveId: int , start_addr: int , output_value):
        try:
            self.tt.msBegin()
            ret = self.master.execute(slaveId, cst.WRITE_MULTIPLE_COILS, start_addr, output_value=output_value)
            print('used time:{}ms'.format(self.tt.msLast()))
            self.logger.info(ret)
            # 判断返回元组信息，ret[0] == 起始地址
            if ret[0] == start_addr:
                # 写入数据成功
                return True
            else:
                return False
        except modbus.ModbusError as exc:
            self.logger.error("%s", exc)
            self.show_modbus_exception(exc.get_exception_code())
            return None
        except Exception as e:
            print("e9:",e)

    # 写单个保持寄存器值（字操作） -- 一般更改某些参数值
    def writeSingleRegister(self, slaveId: int , start_addr: int , output_value):
        try:
            self.tt.msBegin()
            ret = self.master.execute(slaveId, cst.WRITE_SINGLE_REGISTER, start_addr, output_value=output_value)
            #print('used time:{}ms'.format(self.tt.msLast()))
            #self.logger.info(ret)
            # 判断返回元组信息，ret[0] == 起始地址
            if ret[0] == start_addr:
                # 写入数据成功
                return True
            else:
                return False
        except modbus.ModbusError as exc:
            self.logger.error("%s", exc)
            self.show_modbus_exception(exc.get_exception_code())
            return None
        except Exception as e:
            print("e10:",e)


    # 写多个保持寄存器值（字操作） -- 一般更改某些参数值
    def writeMultipleRegister(self, slaveId: int , start_addr: int , output_value):
        try:
            self.tt.msBegin()
            ret = self.master.execute(slaveId, cst.WRITE_MULTIPLE_REGISTERS, start_addr, output_value=output_value)
            print('used time:{}ms'.format(self.tt.msLast()))
            self.logger.info(ret)
            # 判断返回元组信息，ret[0] == 起始地址
            if ret[0] == start_addr:
                # 写入数据成功
                return True
            else:
                return False
        except modbus.ModbusError as exc:
            self.logger.error("%s", exc)
            self.show_modbus_exception(exc.get_exception_code())
            return None
        except Exception as e:
            print("e11:",e)

